namespace ClassyBlog.Infrastructure
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Dynamic;
    using System.Linq;

    public class PropertyAccessor : DynamicObject
    {
        private readonly object target;

        public PropertyAccessor(object target)
        {
            if (target == null)
            {
                throw new ArgumentNullException("target");
            }

            this.target = target;
        }

        public override bool TryGetMember(
            GetMemberBinder binder, out object result)
        {
            if (binder == null)
            {
                throw new ArgumentNullException("binder");
            }

            result = null;

            var property = GetProperty(binder.Name);

            if (property != null)
            {
                result = property.GetValue(target);
            }
            else
            {
                var hasDynamicProperties = target as IHasDynamicProperties;

                if ((hasDynamicProperties != null) &&
                    hasDynamicProperties.DynamicPropertyNames
                                         .Contains(binder.Name))
                {
                    result = hasDynamicProperties.GetDynamicProperty(binder.Name);
                }
            }

            return true;
        }

        public override bool TrySetMember(SetMemberBinder binder, object value)
        {
            if (binder == null)
            {
                throw new ArgumentNullException("binder");
            }

            var property = GetProperty(binder.Name);

            if (property != null)
            {
                if (property.IsReadOnly)
                {
                    return false;
                }

                property.SetValue(target, value);
            }
            else
            {
                var hasDynamicProperties = target as IHasDynamicProperties;

                if (hasDynamicProperties != null)
                {
                    hasDynamicProperties.SetDynamicProperty(binder.Name, value);
                }
            }

            return true;
        }

        public override IEnumerable<string> GetDynamicMemberNames()
        {
            var members = GetProperties().Select(p => p.Name);
            var hasDynamicProperties = target as IHasDynamicProperties;

            if (hasDynamicProperties != null)
            {
                members = members.Union(
                    hasDynamicProperties.DynamicPropertyNames);
            }

            return members;
        }

        private IEnumerable<PropertyDescriptor> GetProperties()
        {
            return TypeDescriptor.GetProperties(target).Cast<PropertyDescriptor>();
        }

        private PropertyDescriptor GetProperty(string name)
        {
            var property = GetProperties().Where(p => p.Name.Equals(name))
                                          .FirstOrDefault();

            return property;
        }
    }
}